package com.rou.im.framework.core.log;

/**
 * <p></p>
 *
 * @author GaoShuangWen
 * @since 2020-05-13 22:50
 */

import java.io.IOException;
import java.nio.channels.Selector;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

public class JLogBase extends Thread {

    JLogBase me = null;
    /**
     * 用于格式化日志文件名的对象
     */
    private SimpleDateFormat headerFormat = null;
    /**
     * 具体的写日志操作的类
     */
    private JLogger log = null;
    /**
     * 在每条日志中显示产生该日志的程序的类名
     */
    private boolean showClassname = true;
    /**
     * 在每条日志中显示产生该日志的程序名
     */
    private boolean showFilename = true;
    /**
     * 在每条日志中显示产生该日志的代码的行号
     */
    private boolean showLineNo = true;
    /**
     * 调用当前类的程序的类名
     */
    private String parentClassname = null;
    StringBuilder sb = new StringBuilder();

    public void setParentClassname(String parentClassname) {
        this.parentClassname = parentClassname;
    }

    public SimpleDateFormat getHeaderFormat() {
        return headerFormat;
    }

    public void setHeaderFormat(SimpleDateFormat headerFormat) {
        this.headerFormat = headerFormat;
    }

    /**
     * 构造函数,用于在标准输出上输出信息
     *
     * @param headerFormat
     *            生成日志前缀的时间格式串
     */
    public JLogBase(SimpleDateFormat headerFormat) {
        this.headerFormat = headerFormat;
    }

    /**
     * 构造函数
     *
     * @param logFilenameTimeFormatString
     *            生成日志文件名的时间格式串
     * @param headerFormat
     *            生成日志前缀的时间格式串
     */
    public JLogBase(String logFilenameTimeFormatString, SimpleDateFormat headerFormat) {
        this.headerFormat = headerFormat;
        log = new JLogger(logFilenameTimeFormatString);
        me = this;
        me.setName(logFilenameTimeFormatString);
        me.start();
    }

    /**
     * 构造函数
     *
     * @param format
     *            生成日志文件名的时间格式串
     * @param headerFormat
     *            生成日志前缀的时间格式串
     * @param timeSpan
     *            日志文件的时间粒度，单位分钟
     */
    public JLogBase(String format, SimpleDateFormat headerFormat, int timeSpan) {
        this.headerFormat = headerFormat;
        log = new JLogger(format);
        log.setTimingSpace(timeSpan * 60000);
        me = this;
        me.setName(format);
        me.start();
    }

    public boolean isShowClassname() {
        return showClassname;
    }

    public void setShowClassname(boolean showClassname) {
        this.showClassname = showClassname;
    }

    public boolean isShowFilename() {
        return showFilename;
    }

    public void setShowFilename(boolean showFilename) {
        this.showFilename = showFilename;
    }

    public boolean isShowLineNo() {
        return showLineNo;
    }

    public void setShowLineNo(boolean showLineNo) {
        this.showLineNo = showLineNo;
    }

    /**
     * 将参数中的字符串合并成一个字符串
     *
     * @param text
     *            要合并的第一个字符串
     * @param msgs
     *            对象的集合
     * @return
     */
    private String catStrings(String text, Object... msgs) {
        if (text != null) {
            sb.append(text);
        }
        if (msgs != null && msgs.length > 0) {
            try {
                for (Object obj : msgs) {
                    if (obj != null) {
                        String tmp = obj.toString();
                        if (tmp != null) {
                            sb.append(tmp);
                        }
                    }
                }
            } catch (Exception ex) {
                LOG.error(LOG.getTrace(ex));
            }
        }
        return sb.toString();
    }

    /**
     * 构造产生日志的类的附件信息（类名、文件名和行号）
     *
     * @return
     */
    private String getTraceInfo() {
        if (showClassname || this.showFilename || this.showLineNo) {
            String clsName;
            if (this.parentClassname == null) {
                clsName = JLogBase.class.getName();
            } else {
                clsName = this.parentClassname;
            }
            StackTraceElement[] stacks = new Throwable().getStackTrace();
            int index = 0;
            for (int i = 0; i < stacks.length; i++) {
                if (clsName.equals(stacks[i].getClassName())) {
                    index = i + 1;
                    break;
                }
            }

            if (index < stacks.length) {
                if (isShowClassname()) {
                    String cls = stacks[index].getClassName();
                    int pos = cls.indexOf("$");
                    if (pos > 0) {
                        sb.append(cls.substring(0, pos));
                    } else {
                        sb.append(cls);
                    }
                    if (isShowLineNo()) {
                        sb.append(" (line " + stacks[index].getLineNumber() + ")");
                    }
                } else if (isShowFilename()) {
                    sb.append(stacks[index].getFileName());
                    if (isShowLineNo()) {
                        sb.append(" (line " + stacks[index].getLineNumber() + ")");
                    }
                } else if (isShowLineNo()) {
                    sb.append(" (line " + stacks[index].getLineNumber() + ")");
                }
            }

            if (sb.length() == 0) {
                if (isShowClassname()) {
                    String cls = stacks[0].getClassName();
                    int pos = cls.indexOf("$");
                    if (pos > 0) {
                        sb.append(cls.substring(0, pos));
                    } else {
                        sb.append(cls);
                    }
                    if (isShowLineNo()) {
                        sb.append(" (line " + stacks[0].getLineNumber() + ")");
                    }
                } else if (isShowFilename()) {
                    sb.append(stacks[0].getFileName());
                    if (isShowLineNo()) {
                        sb.append(" (line " + stacks[0].getLineNumber() + ")");
                    }
                } else if (isShowLineNo()) {
                    sb.append(" (line " + stacks[0].getLineNumber() + ")");
                }
            }
            return sb.toString();
        } else {
            return "";
        }
    }

    /**
     * 将信息记录到日志文件中
     *
     * @param preHit
     *            日志内容前要添加的内容
     * @param msgs
     *            要记录的日志内容
     */
    public synchronized void trace(String preHit, Object... msgs) {
        if (log == null)
            return;
        String preTime = "";
        long tm = System.currentTimeMillis();

        if (headerFormat != null) {
            preTime = headerFormat.format(new Date(tm));// 写入当前日志的前缀
        }
        sb.delete(0, sb.length());
        String progMsg = getTraceInfo();
        String msg = null;
        if (preHit == null)
            preHit = "";

        sb.delete(0, sb.length());
        msg = catStrings(preHit + preTime + " " + progMsg + " ", msgs);
        try {
            if (me == null) {
                if (!log.append(tm, msg)) {
                    System.err.println(log.getErrString());
                }
            } else {
                logBlockingQueue.add(new LogInfo(tm, msg));
            }
        } catch (IOException ex) {
            LOG.error(LOG.getTrace(ex));
        }
    }

    public synchronized void trace(String msg) {
        if (log == null)
            return;
        long tm = System.currentTimeMillis();
        sb.delete(0, sb.length());
        sb.delete(0, sb.length());
        try {
            if (me == null) {
                if (!log.append(tm, msg)) {
                    System.err.println(log.getErrString());
                }
            } else {
                logBlockingQueue.add(new LogInfo(tm, msg));
            }
        } catch (IOException ex) {
            LOG.error(LOG.getTrace(ex));
        }
    }

    /**
     * 将信息打印的屏幕上
     *
     * @param preHit
     *            日志内容前要添加的内容
     * @param msgs
     *            要打印的日志内容
     */
    public synchronized void print(String preHit, Object... msgs) {
        String preTime = "";
        long tm = System.currentTimeMillis();

        if (headerFormat != null) {
            preTime = headerFormat.format(new Date(tm));// 写入当前日志的前缀
        }
        sb.delete(0, sb.length());
        String progMsg = getTraceInfo();
        String msg = null;
        if (preHit == null)
            preHit = "";

        sb.delete(0, sb.length());
        msg = catStrings(preHit + preTime + " " + progMsg + " ", msgs);
    }

    private BlockingQueue<LogInfo> logBlockingQueue = new LinkedBlockingQueue<LogInfo>();

    static {
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                if (t instanceof JLogBase) {
                    LOG.error("FAT: Thread ", JLogBase.class.getName(), " died due to ", E.formatThrowable(e));
                }
            }
        });

        /**
         * this is to avoid the jvm bug: NullPointerException in Selector.open()
         * http://bugs.sun.com/view_bug.do?bug_id=6427854
         */
        try {
            Selector.open().close();
        } catch (IOException e) {
            LOG.error("Selector failed to open", E.formatException(e));
        }
    }

    @Override
    public void start() {
        // ensure thread is started once and only once
        if (getState() == Thread.State.NEW) {
            setDaemon(true); // 确保程序退出时，虚拟机会终止daemon线程
            super.start();
        }
    }

    @Override
    public void run() {
        while (true) {
            try {
                LogInfo msg = logBlockingQueue.poll(1, TimeUnit.SECONDS);
                if (msg != null && log != null) {
                    log.append(msg.getLogTime(), msg.getLogMessage());
                }
            } catch (InterruptedException e) {
                LOG.error(LOG.getTrace(e));
            } catch (Exception e) {
                e.printStackTrace();
                LOG.error(LOG.getTrace(e));
            }
        }
    }

    class LogInfo {

        private long logTime;
        private String logMessage;

        public LogInfo(long tm, String msg) {
            this.logMessage = msg;
            this.logTime = tm;
        }

        public long getLogTime() {
            return logTime;
        }

        public void setLogTime(long logTime) {
            this.logTime = logTime;
        }

        public String getLogMessage() {
            return logMessage;
        }

        public void setLogMessage(String logMessage) {
            this.logMessage = logMessage;
        }

    }

}
